import os
import sys
import json
import time

import traceback

import shell

import datetime

from server import TCPServer
from server import BaseRequestHandler

from threading import Thread

import re

import logger
from logger import log

from hj212_msg import hj212_msg
from utils import *

import prettytable as pt

'''
".*CN=2011;.*",
".*CN=2021;.*",
".*CN=2051;.*",
".*CN=2061;.*",
".*CN=2031;.*",
".*CN=2041;.*",
".*CN=2081;.*",
".*CN=3019;.*",
".*CN=3020;.*"
'''

def addr_to_string(addr):
    return ("%s_%d"%(addr[0], addr[1])).replace(".", "_")
    
def current_time():
    return str(datetime.datetime.now())

class HJ212DeviceTask:
    def __init__(self, **args):
        self.args = args

class HJ212Device:
    client_address = []
    task = []
    def __init__(self, server_address, client_address, MN, interface, **options):
        self.server_address = server_address
        self.client_address.append(client_address)
        self.MN = MN
        self.interface = interface
        self.options = options

    def accept(self, msg, clientaddr): 
        if clientaddr != self.client_address[-1]:
            self.client_address.append(clientaddr)

        if "QN" not in msg.msg["data"]:
            msg.msg["data"]["QN"] = ""

        self.save_record(msg)   # 保存记录
        
        for cfg in self.options["reply_cfg"]:
            for pattern in cfg["trigger"]["pattern"]:
                if re.match(pattern, msg.msg["raw-data"]):
                    send_content = cfg["context"].format(recv = msg.msg)
                    send_content = "##%04d%s0000\r\n"%(len(send_content.encode("utf-8")), send_content)
                    self.sendall(send_content.encode("utf-8"))
                    break

        timestamp = time.time()
        tasks = []
        for task in self.task:
            if task.args["taskid"] == msg.msg["data"]["QN"]:
                string = "%s, server=%s, client=%s\n"%(current_time(), str(self.server_address), str(self.client_address[-1]))
                string += msg.table() + '\n'
                # string += str((msg.item("raw-data") if isinstance(msg.item("raw-data"), str) else msg.item("raw-data")).encode("utf-8") + '\n' * 3)
                string += str(msg.item("raw-data").encode("utf-8") if isinstance(msg.item("raw-data"), str) else msg.item("raw-data")) + "\n" * 3
                save_to_file(get_path("request", task.args["task"]["taskname"], datetime.datetime.fromtimestamp(task.args["timestamp"]).strftime("%Y-%m-%d&%H-%M-%S&%f")[:-3]), string)

            if(timestamp - task.args["timestamp"]) >= task.args["task"]["time-to-live"]:
                tasks.append(task)
        for task in tasks:
            self.task.remove(task)

    def save_record(self, record):
        # log.debug(str(record.msg))
        self.record = record
        self.save_record_to_file(record)

    def save_record_to_file(self, record):
        path = get_path(addr_to_string(self.server_address), str(record.item("data/MN")), str(record.item("data/CN")))
        string = "%s, server=%s, client=%s\n"%(current_time(), str(self.server_address), str(self.client_address[-1]))
        string += record.table() + '\n'
        raw = record.item("raw-data")
        if isinstance(raw, str):
            string += str(raw.encode("utf-8")) + '\n' * 3
        save_to_file(path, string)
        log.debug("CN=%s,MN=%s,DT=%s"%(str(record.item("data/CN")), str(record.item("data/MN")), str(record.item("data/CP/DataTime"))))

    def updata_interface(self, interface):
        self.interface = interface

    def recv(self):
        if self.interface:
            return self.interface.recv()

    def sendall(self, data):
        if self.interface:
            return self.interface.sendall(data)

    def runtask(self, taskid, **args):
        log.info("Dev<{0}> request-id<{1}>: cn={2} args={3}".format(self.MN, time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(taskid)), args["code"], args))
        if self.record:
            argv = self.record.msg
            argv["taskid"] = datetime.datetime.fromtimestamp(taskid).strftime("%Y%m%d%H%M%S%f")[:-3]
            argv["timestamp"] = taskid
            argv["task"] = args
            send_content = args["request-context"].format(argv=argv)
            send_content = "##%04d%s0000\r\n"%(len(send_content.encode("utf-8")), send_content)
            self.sendall(send_content.encode("utf-8"))
            self.task.append(HJ212DeviceTask(**argv))
            log.info("request: <{0}>".format(send_content).encode("utf-8"))

            string = "%s, server=%s, client=%s\n"%(current_time(), str(self.server_address), str(self.client_address[-1]))
            string += hj212_msg(send_content).table() + '\n'
            string += str(send_content.encode("utf-8")) + '\n' * 3
            save_to_file(get_path("request", argv["task"]["taskname"], datetime.datetime.fromtimestamp(argv["timestamp"]).strftime("%Y-%m-%d&%H-%M-%S&%f")[:-3]), string)


class HJ212ServerHandler(BaseRequestHandler):

    last_MN = None

    def setup(self):
        self.buf = bytes("", "ascii")
        return super().setup()
    def finish(self):
        self.server.clients[self.last_MN].updata_interface(None)
        return super().finish()
    def recv(self):
        # self.buf += super().recv()
        buf = super().recv()
        # --------保存原始数据--------
        path = get_path(addr_to_string(self.server.server_address), "RAW", addr_to_string(self.client_address))
        string = "%s\n%s\n\n"%(current_time(), str(buf))
        save_to_file(path, string)
        # ---------------------------
        self.buf += buf
        data = []
        while "\n".encode("utf-8") in self.buf:
            index = self.buf.find("\n".encode("utf-8")) + 1
            data.append(self.buf[ : index])
            self.buf = self.buf[index : ]
            # log.debug("\nself.buf. (%s)\ndata. (%s)", str(self.buf), str(data))
            # self.sendall(data)
        return data
    def recv_handle(self, data):
        # log.debug(str(data))
        for msg in data:
        #    try:
        #        msg = msg.decode("utf-8")
        #    except Exception as e:
        #        return

            msg = msg.decode("utf-8")
            self.hj212_parse(msg)

    def hj212_parse(self, string):
        # log.debug(string)
        # print(string)
        msg = hj212_msg(string)
        MN = msg.item("data/MN")
        if not MN in self.server.clients:
            self.server.clients[MN] = HJ212Device(self.server.server_address, self.client_address, MN, self, reply_cfg=self.server.argv["server"].options["reply"])
        else:
            self.server.clients[MN].updata_interface(self)
        self.last_MN = MN
        self.server.clients[MN].accept(msg, self.client_address)

class HJ212Server:
    server = []       # 服务器
    device = []
    def __init__(self, config, **option):
        for addr in config["addresses"]:
            ip = addr["ip"]
            port = addr["port"]
            self.options = option
            server = TCPServer(server_address=(ip, port), RequestHandlerClass=HJ212ServerHandler, isAutoStart=False, server=self)
            self.server.append(server)
    def disconnect(self):
        for server in self.server:
            Thread(target=server.disconnect_all).start()
    def stop_allserver(self):
        for server in self.server:
            Thread(target=server.stop).start()
    def stop_server(self, **option):
        if "addr" in option:
            pass
        if "index" in option:
            index = option["index"]
            if index < len(self.server):
                Thread(target=self.server[index].stop).start()
            else:
                log.error("index error")
    def start_allserver(self):
        start_thread = []
        for server in self.server:
            # Thread(target=server.start).start()
            tid = Thread(target=server.start)
            start_thread.append(tid)
            tid.start()
        for thread in start_thread:
            thread.join()
            
    def start_server(self, **option):
        if "addr" in option:
            pass
        if "index" in option:
            index = option["index"]
            if index < len(self.server):
                Thread(target=self.server[index].start).start()
            else:
                log.error("index error")
    
    def batch_control_device(self, task):
        assert(task["task-type"] == "control-device")
        taskid = time.time()
        for server in self.server:
            if not server.server:
                continue
            for deviceid in server.server.clients:
                if deviceid in task["device-list"]:
                    server.server.clients[deviceid].runtask(taskid, **task)


class Shell(shell.shell):

    def do_info(self, arg):
        global hj212
        log.info(str(hj212.__dict__))
        log.info('\n'.join(['%s:%s' % item for item in hj212.__dict__.items()]))

    def do_disconnect(self, arg):
        hj212.disconnect()

    def do_server_stop(self, arg):      # 停止服务器
        if arg == "":
            hj212.stop_allserver()
        else:
            hj212.stop_server(index=int(arg))
    def do_server_start(self, arg):     # 启动服务器
        if arg == "":
            hj212.start_allserver()
        else:
            hj212.start_server(index=int(arg))
    def do_server(self, arg):           # 列出服务器列表
        string = '\n'
        index = 0
        tb = pt.PrettyTable()
        tb.field_names = ["index", "addr", "state"]
        for server in hj212.server:
            tb.add_row([index, str(server.server_address), server.isStart()])
            index += 1
        string += str(tb)
        log.info(string)

    def do_socket_stop(self, arg):      # 停止一个client socket
        arg = arg.split()
        if len(arg) != 2:
            log.error("输入错误")
            return False
        server_index = int(arg[0])
        client_index = int(arg[1])
        if server_index >= len(hj212.server):
            log.error("server index 错误")
            return False
        if client_index >= len(hj212.server[server_index].get_client()):
            log.error("client index 错误")
            return False
        addr = hj212.server[server_index].get_client()[client_index]
        if hj212.server[server_index].disconnect:
            hj212.server[server_index].disconnect(addr)
    def do_socket(self, arg):           # 列出socket
        string = '\n'
        server_index = 0
        tb = pt.PrettyTable()
        tb.field_names = ["socket-type", "server-addr", "server-state", "server-index", "client-addr", "client-index"]
        for server in hj212.server:
            client_index = 0
            tb.add_row(["server", str(server.server_address), server.isStart(), server_index, "", ""])
            for client_addr in server.get_client():
                tb.add_row(["client", str(server.server_address), server.isStart(), server_index, client_addr, client_index])
                client_index += 1
            server_index += 1
        string += str(tb)
        log.info(string)

    def do_client(self, arg):
        index = 0
        string = '\n'
        tb = pt.PrettyTable()
        tb.field_names = ["server", "client-mn"]
        for server in hj212.server:
            for client in server.get_handle():
                tb.add_row([index, client])
            index += 1
        string += str(tb)
        log.info(string)

    def do_client_send(self, arg):
        arg = arg.split()
        if len(arg) < 3:
            log.error("输入错误")
            return False
        server_index = int(arg[0])
        device_mn = arg[1]
        del arg[0]
        del arg[0]
        for data in arg:
            hj212.server[server_index].get_handle()[device_mn].sendall(data.encode("utf-8"))
            log.info("send (server-%d)/%s-> (%s)"%(server_index, device_mn, data))

    def do_runtask(self, arg):
        with open("task.json", "r", encoding="utf-8") as f:
            task_json = json.load(f)
        print(task_json)
        for task in task_json:
            if task["task-type"] == "control-device":
                # 反控HJ212客户端
                hj212.batch_control_device(task)
        




if __name__ == '__main__':
    # config = {}
    with open("config/config.json", "r") as f:
        config = json.load(f)
    hj212 = HJ212Server(config["server"], reply = config["reply"])
    hj212.start_allserver()
    sh = Shell()

    try:
        sh.cmdloop()
    except Exception as e:
        log.error("Shell-Exception(%s)\n%s"%(str(e), traceback.format_exc()))
    hj212.stop_allserver()

